﻿#include "FITKGraphObjectGeoPartFeaturePreview.h"

// VTK
#include <vtkObject.h>
#include <vtkProperty.h>
#include <vtkUnstructuredGrid.h>
#include <vtkPoints.h>

// Graph 
#include "FITKGraphCommons.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"

// Data
#include "FITK_Component/FITKAbaqusData/FITKAbaqusPart.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKAbsGeoCommand.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKAbsGeoShapeAgent.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKVirtualTopoManager.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKGeometryMeshVS.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKGeometryMeshEntity.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKGeoInterfaceFactory.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKAbsGeomTools.h"

namespace Exchange
{
    FITKGraphObjectGeoPartFeaturePreview::FITKGraphObjectGeoPartFeaturePreview(AbaqusData::FITKDataCase* caseData, AbaqusData::FITKAbaqusPart* part)
        : FITKGraphObject3D(caseData, part)
    {
        if (!part)
        {
            return;
        }

        // Initialize polygon offset.
        m_polygonOffset = -5.;

        // Initialize.
        init();

        // Generate data set.
        bool flag = generateDataSet();
        if (!flag)
        {
            return;
        }

        // Generate the shape data and actors.
        generateGraph();

        // Save the data type.
        m_graphInfo.Type = GraphDataType::GeometryPartFeatureGraph;
        m_graphInfo.PartId = part->getDataObjectID();

        // Set the layer need to be rendered.
        setRenderLayer(0, 2);

        // Transparency.
        // setTransparent(true);
    }

    FITKGraphObjectGeoPartFeaturePreview::~FITKGraphObjectGeoPartFeaturePreview()
    {
        // Delete vtk objects.
        deleteVtkObj(m_ugridEdge);
        deleteVtkObj(m_ugridFace);
    }

    void FITKGraphObjectGeoPartFeaturePreview::init()
    {
        // Create the data set.
        m_ugridEdge = vtkUnstructuredGrid::New();
        m_ugridEdge->SetPoints(vtkSmartPointer<vtkPoints>::New());
        m_ugridFace = vtkUnstructuredGrid::New();
        m_ugridFace->SetPoints(vtkSmartPointer<vtkPoints>::New()); 
    }
   
    void FITKGraphObjectGeoPartFeaturePreview::update(bool forceUpdate)
    {
        // Get the part data.
        AbaqusData::FITKAbaqusPart* part = dynamic_cast<AbaqusData::FITKAbaqusPart*>(_dataObj);
        if (!part)
        {
            return;
        }

        Interface::FITKAbsGeoCommand* partCmd = part->getGeoCommandObj();
        if (!partCmd)
        {
            return;
        }

        Interface::FITKAbsGeoShapeAgent* shapeAgent = partCmd->getShapeAgent();
        if (!shapeAgent)
        {
            return;
        }

        // Update visibility.
        updateVisibility();

        // Check the geometry state.
        QVariant newState;
        shapeAgent->createShapeState(newState);

        if (m_shapeState == newState && !forceUpdate)
        {
            return;
        }

        // Clear data.
        clearData();

        // Re-generate the shape.
        bool flag = generateDataSet();

        // Update the actors.
        if (flag)
        {
            updateActors();
        }
    }

    void FITKGraphObjectGeoPartFeaturePreview::setTransparent(bool isOn)
    {
        // Get the opacity.
        double opa = isOn ? 1. - FITKGraphCommons::s_transparency : 1.;

        if (m_fActorFace)
        {
            m_fActorFace->GetProperty()->SetOpacity(opa);
        }

        if (m_fActorEdge)
        {
            m_fActorEdge->GetProperty()->SetOpacity(opa);
        }
    }

    void FITKGraphObjectGeoPartFeaturePreview::setPickable(bool isPickable, int dim)
    {
        Q_UNUSED(dim);

        // Face.
        if (m_fActorFace)
        {
            m_fActorFace->SetPickable(isPickable);
        }
    }

    QVector<int> FITKGraphObjectGeoPartFeaturePreview::getFaceIdsByFaceId(int faceId)
    {
        return m_faceIdFeatFaceIdsMap.value(faceId, QVector<int>());
    }

    int FITKGraphObjectGeoPartFeaturePreview::getCommandIdByCellId(int cellId)
    {
        return m_cellIdCmdIdMap.value(cellId, -1);
    }

    QVector<int> FITKGraphObjectGeoPartFeaturePreview::getCellIdByFaceId(int faceId)
    {
        return m_faceIdCellIdsMap.value(faceId, QVector<int>());
    }

    int FITKGraphObjectGeoPartFeaturePreview::getFaceIdByCellId(int cellId)
    {
        return m_cellIdFaceIdMap.value(cellId, -1);
    }

    QVector<int> FITKGraphObjectGeoPartFeaturePreview::getEdgeCellIdsByFaceId(int faceId)
    {
        return m_faceIdEdgeCellIdsMap.value(faceId, QVector<int>());
    }

    vtkDataSet* FITKGraphObjectGeoPartFeaturePreview::getMesh(int dim)
    {
        switch (dim)
        {
        case 1:
            return m_ugridEdge;
        case 2:
            return m_ugridFace;
        default:
            return nullptr;
        }

        return nullptr;
    }

    bool FITKGraphObjectGeoPartFeaturePreview::generateDataSet()
    {
        // Get the part data.
        AbaqusData::FITKAbaqusPart* part = dynamic_cast<AbaqusData::FITKAbaqusPart*>(_dataObj);
        if (!part)
        {
            return false;
        }

        // Create the feature tool.
        Interface::FITKAbstractGeomToolsCreator* geomTools = Interface::FITKInterfaceGeometryFactory::getInstance()->getGeomToolsCreator();
        if (!geomTools)
        {
            return false;
        }

        Interface::FITKAbstractGeomFeatureTool* featTool = geomTools->createFeatureTool();
        if (!featTool)
        {
            return false;
        }

        Interface::FITKAbsGeoCommand* partCmd = part->getGeoCommandObj();
        Interface::FITKGeoCommandManager* partCmdMgr = dynamic_cast<Interface::FITKGeoCommandManager*>(partCmd);
        if (!partCmdMgr)
        {
            return false;
        }

        vtkPoints* pointsFace = m_ugridFace->GetPoints();
        vtkPoints* pointsEdge = m_ugridEdge->GetPoints();

        int partGeomId = partCmd->getDataObjectID();

        // Get the newest state id of the shape.
        Interface::FITKAbsGeoShapeAgent* shapeAgent = partCmd->getShapeAgent();
        QVariant newState;
        bool flag = shapeAgent->createShapeState(newState);
        if (!flag)
        {
            return false;
        }

        int ptIndexStart = 0, cellIndex = 0;

        // Create datasets for all sub-commands.
        int nSubCmd = partCmdMgr->getDataCount();
        for (int i = 0; i < nSubCmd; i++)
        {
            // Get the sub-command.
            Interface::FITKAbsGeoCommand* subCmd = partCmdMgr->getDataByIndex(i);
            if (!subCmd)
            {
                continue;
            }

            // Check if the command has been referred.
            int nRefed = subCmd->getReferencedCmdCount();
            if (nRefed != 0)
            {
                continue;
            }

            // Get the abstract shape agent.
            Interface::FITKAbsGeoShapeAgent* shapeAgentSub = subCmd->getShapeAgent();
            if (!shapeAgentSub)
            {
                continue;
            }

            // Get the sub-command id.
            int subCmdId = subCmd->getDataObjectID();

            // Get the trianglation of the shape.
            Interface::FITKGeometryMeshVS* meshVS = shapeAgentSub->getMeshVS();
            Interface::FITKVirtualTopoManager* topoMgr = shapeAgentSub->getVirtualTopoManager();
            if (!meshVS || !topoMgr)
            {
                continue;
            }

            const QHash<int, Interface::FITKGeoMeshVSFaceEntity*> faceMesh = meshVS->getFaceItems();
            const QHash<int, Interface::FITKGeoMeshVSEdgeEntity*> edgeMesh = meshVS->getEdgeItems();

            // Get all type of feature faces.
            QList<QList<Interface::FITKAbsVirtualTopo*>> featFacesGroups;
            featTool->getFeatures(subCmdId, Interface::FITKGeoEnum::FeatureTypeAll, featFacesGroups);

            // Create data set.
            for (const QList<Interface::FITKAbsVirtualTopo*> & featFaces : featFacesGroups)
            {
                QVector<int> featureFaceIds;

                for (Interface::FITKAbsVirtualTopo* featFace : featFaces)
                {
                    int faceId = featFace->getDataObjectID();
                    Interface::FITKGeoMeshVSFaceEntity* faceTris = faceMesh.value(faceId);
                    if (!faceTris)
                    {
                        break;
                    }

                    featureFaceIds.push_back(faceId);

                    // Create face.
                    //@{
                    // Get all points of the face.
                    const QList<Interface::FITKGeoMeshVSPt*> & facePoints = faceTris->getPoints();
                    for (Interface::FITKGeoMeshVSPt* facePoint : facePoints)
                    {
                        pointsFace->InsertNextPoint(facePoint->x(), facePoint->y(), facePoint->z());
                    }

                    // Get face triangles.
                    const QList<Interface::FITKGeoMeshVSTri*> & tris = faceTris->getTriangles();

                    for (Interface::FITKGeoMeshVSTri* tri : tris)
                    {
                        // Get points indice pf the triangle.
                        const int* nodes = tri->getNode();

                        // Get points index in VTK grid of vertice
                        int ptIndex1 = ptIndexStart + nodes[0];
                        int ptIndex2 = ptIndexStart + nodes[1];
                        int ptIndex3 = ptIndexStart + nodes[2];

                        // Add cell.
                        vtkSmartPointer<vtkIdList> triCell = vtkSmartPointer<vtkIdList>::New();
                        triCell->InsertNextId(ptIndex1);
                        triCell->InsertNextId(ptIndex2);
                        triCell->InsertNextId(ptIndex3);
                        cellIndex = m_ugridFace->InsertNextCell(VTKCellType::VTK_TRIANGLE, triCell);

                        // Save the face id.
                        m_cellIdCmdIdMap.push_back(subCmdId);
                        m_cellIdFaceIdMap.push_back(faceId);
                        m_faceIdCellIdsMap[faceId].push_back(cellIndex);
                    }

                    // Update the point start index.
                    ptIndexStart += facePoints.count();
                    //@}

                    // Create face edges.
                    //@{
                    int nEdge = featFace->getSubTopoCount();
                    for (int i = 0; i < nEdge; i++)
                    {
                        Interface::FITKAbsVirtualTopo* eTopo = featFace->getSubTopo(i);
                        if (!eTopo)
                        {
                            continue;
                        }

                        int edgeId = eTopo->getDataObjectID();
                        Interface::FITKGeoMeshVSEdgeEntity* edge = edgeMesh[edgeId];

                        // Get edge points.
                        const QList<Interface::FITKGeoMeshVSPt*> & pts = edge->getPoints();

                        // Create poly line cell.
                        //@{
                        vtkSmartPointer<vtkIdList> polyLineCell = vtkSmartPointer<vtkIdList>::New();

                        for (Interface::FITKGeoMeshVSPt* pt : pts)
                        {
                            // Add point.
                            int ptIndex = pointsEdge->InsertNextPoint(pt->x(), pt->y(), pt->z());
                            polyLineCell->InsertNextId(ptIndex);
                        }

                        // Add cell.
                        int edgeCellId = m_ugridEdge->InsertNextCell(VTKCellType::VTK_POLY_LINE, polyLineCell);

                        // Save the face id.
                        m_faceIdEdgeCellIdsMap[faceId].push_back(edgeCellId);
                    }
                    //@}
                }

                // Save the face id and its group.
                for (const int & faceId : featureFaceIds)
                {
                    m_faceIdFeatFaceIdsMap.insert(faceId, featureFaceIds);
                }                     
            }
        }

        // Save the shape state.
        m_shapeState = newState;

        return true;
    }

    void FITKGraphObjectGeoPartFeaturePreview::generateGraph()
    {
        // Create edge actor.
        m_fActorEdge = FITKGraphActor::New();
        m_fActorEdge->setGraphObject(this);
        m_fActorEdge->setActorType(ActorType::EdgeActor);
        m_fActorEdge->SetPickable(false);
        m_fActorEdge->SetVisibility(true);
        m_fActorEdge->GetProperty()->SetLineWidth(2);
        m_fActorEdge->GetProperty()->SetRepresentation(1);
        m_fActorEdge->setRelativeCoincidentTopologyPolygonOffsetParameters(m_polygonOffset);
        addActor(m_fActorEdge);

        // Create face actor.
        m_fActorFace = FITKGraphActor::New();
        m_fActorFace->setGraphObject(this);
        m_fActorFace->setActorType(ActorType::SurfaceActor);
        m_fActorFace->SetPickable(false);
        m_fActorFace->SetVisibility(true);
        m_fActorFace->GetProperty()->SetRepresentation(2);
        m_fActorFace->GetProperty()->SetEdgeVisibility(false);
        m_fActorFace->GetProperty()->SetLineWidth(0.);
        m_fActorFace->setRelativeCoincidentTopologyPolygonOffsetParameters(m_polygonOffset);
        addActor(m_fActorFace);

        m_fActorEdge->setInputDataObject(m_ugridEdge);
        m_fActorFace->setInputDataObject(m_ugridFace);

        setLightPropertiesForLine(m_fActorEdge);
        setLightPropertiesForSurface(m_fActorFace);

        // Set color.
        //@{
        m_fActorEdge->setColor(FITKGraphCommons::s_featurePreviewColor);
        m_fActorFace->setColor(FITKGraphCommons::s_featurePreviewColor);
        //@}
    }

    void FITKGraphObjectGeoPartFeaturePreview::clearData()
    {
        // Reset vtk objects.
        resetVtkObj(m_ugridEdge);
        resetVtkObj(m_ugridEdge->GetPoints());
        resetVtkObj(m_ugridFace);
        resetVtkObj(m_ugridFace->GetPoints());

        // Clear dicts.
        m_cellIdCmdIdMap.clear();
        m_cellIdFaceIdMap.clear();
        m_faceIdFeatFaceIdsMap.clear();
        m_faceIdCellIdsMap.clear();
        m_faceIdEdgeCellIdsMap.clear();
    }
}   // namespace Exchange